/**
 * @file worker.c
 * @author Marco Luisi
 *  Created on: 15 Dec 2010
 *      Author: marco
 */


#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>



#include "worker.h"
#include "genHash.h"
#include "genList.h"
#include "msg.h"
#include "comsock.h"
volatile sig_atomic_t sgpipe = 0;
volatile sig_atomic_t sgn = 0;

/*Variabili globali ma locali al thread*/
__thread sigset_t set; //spostare dentro un init?
__thread message_t incoming;
__thread message_t response;
__thread int fd;// cambiare nome
__thread user* current_user;
__thread char* current_name;
__thread msg_buffer in_buf;


/*Var extern dichiarate in msgserver*/
extern int pending_signal;
extern hashTable_t* users;

extern user_pointers_l *online_list;
extern pthread_mutex_t users_mtx;
extern pthread_mutex_t online_mtx;

/*Var extern del writer*/
extern pthread_cond_t empty_cond;
extern pthread_cond_t full_cond;
extern pthread_mutex_t queue_mtx; /* mutex*/
extern msg_queue writer_queue;




/**
 *
 * @param ff
 */
void* worker_start(void* u){
	int rcv;
	current_user = (user*)u;
	fd = current_user->sfd;
	current_name = current_user->name;

	struct sigaction *sa = calloc(sizeof(struct sigaction), 1);
	/*Si mascherano tutti i segnali per il thread worker*/
	sigfillset(&set);//fare una ec per i thread che fa return void
	pthread_sigmask(SIG_SETMASK,&set,NULL);
	memset(sa,sizeof(sa),0);
	sa->sa_handler = SIG_IGN;
	sigaction(SIGQUIT, sa, NULL);
	//sigaction(SIGPIPE, sa, NULL);//pure qui

	//prova di un sigpipe temporaneo
	/*Handlers del thread*/
	sa->sa_handler = sigpipe_handler;
	sigaction(SIGPIPE, sa, NULL);

	sa->sa_handler = sigusr1_handler;
	sigaction(SIGUSR1, sa, NULL);

	free(sa);//ok ma perchè?


	/*Handlers del thread*/

	//continua ad ignorare sigpipe


	/*Si comunica al client il login avvenuto con successo*/
	newMessage(MSG_OK,NULL, &response,0);
	//fare controlli anche sulla send
	sendMessage(fd, &response);
	/*Utente autenticato e connesso*/
	//controllo su null del msg
	//oppure un controllo dentro receive in caso di broken pipe
	/*Ciclo di ascolto del socket*/
	while (pending_signal == 0){
		if(pending_signal != 0){//togliere non serve
			worker_stop();
		}
		/*Lettura socket, si attende messaggio dal client*/
		rcv = receiveMessage(fd, &incoming);
		//printf("worker: dopo receive rcv = %d\n",rcv);

		//alla exit quando client chiude il socket, passa sto if
		/*Se il client si è disconnesso senza comunicare l'uscita*/
		if(rcv == SEOF){
				//printf("Worker: client per %s disconnesso!\n",current_name);
				worker_stop();
				//uscita?
				break;
			}
		if(rcv < 0){ // - 1 prima
			//printf("worker: errore receive\n");//fare una stampa ammodo
			perror("rcv < 0");
			worker_stop(); //fare funz di uscita
			break;
		}
		/*Si agisce a seconda del tipo di messaggio ricevuto*/
		switch(incoming.type){

		case(MSG_EXIT):{
			worker_stop();
			break;
		}
		case(MSG_TO_ONE):{
			to_one(&incoming);

			break;
		}
		case(MSG_BCAST):{
			//printf("worker: broadcast!\n");
			broadcast(&incoming);
			break;

		}
		case(MSG_LIST):{
			//printf("worker: list!\n");
			userlist();
			break; //manderei solo uno \n e len 1 se nessuno online?
		}

		}

		//FARE FREEE SVUOTARE BUFFFERS!!!!!!!
	}

    if (pending_signal == 1){
            newMessage(MSG_NO,NULL, &response,0);
            sendMessage(fd, &response);
    }

	worker_stop();
	return;
}

/*Comandi utente*/

/**
 *
 * @param m
 * @return
 */
int to_one(message_t* m){
	char *text;
	user *u = NULL;
	user *check = NULL;
	unsigned int len;
	msg_buffer b;
	char *log;
	size_t log_len;

	extract(m,&b);
	//printf("TO ONE RICEVE !%s! PER !%s!\n",b.msg,b.name);
	//RICONTROLLARE DI FARE FREE SUI BUFFER PRIMA E DOPOOOOO

	//da mettere i lock!! aggiungere link alla mutex nella struct lista?
	u = search_user_l(online_list, b.name);
	//if(tmp == NULL){
	if(u == NULL){
		/*Si controlla se è almeno presente nella lista utenti*/
		check = (user*)find_hashElement(users, b.name);
		/*Utente non in lista*/
		if(check == NULL){
			//fare un errore qui per utente sconsociuto?!
			len = strlen(b.name) + 31;//err_nc  //+1 //prima era b.name
			EC_NULL_E(text = malloc(len) ,ERR_MEM);

			len = snprintf(text,len,ERR_NC,b.name);
			newMessage(MSG_ERROR,text, &response,len);
			sendMessage(current_user->sfd, &response);
			return -1;
		}
		/*Utente in lista ma offline*/

		//OOOOOOOOOOOOO TESTARE LA FREE USER
		//metterla poi
		free(check);
		//prima era b.name
		len = strlen(b.name) + 31;//err_nc  //+1
		EC_NULL_E(text = malloc(len) ,ERR_MEM);
		// "[ERROR] %s: utente non connesso" ERR_NC
		len = snprintf(text,len,"[ERROR] %s: utente non connesso",b.name);
		//printf("worker: error = !%s!\n",text);

		//fare una sola send tra sconosciuto e non online
		newMessage(MSG_ERROR,text, &response,len);
		sendMessage(current_user->sfd, &response);
		//FREEEE SUI BUFFERS

		return -1;
	}

	/*Preparazione e invio del messaggio per il destinatario*/
	len = strlen(current_name)+ strlen(b.msg) + 5;// aggiunto +1
	EC_NULL_E(text = malloc(len) ,ERR_MEM);
	snprintf(text,len,"[%s] %s",current_name,b.msg);
	newMessage(MSG_TO_ONE,text, &response,len+1); //tanto per eh
	//printf("TO ONE SPEDISCE !%s! a !%s! len  = %d\n",response.buffer, u->name, response.length);
	sendMessage(u->sfd, &response);

	/*Si prepara la riga per il log*/
	log_len = 3; /*i due : , \n e */ // \0 è in lenght
	log_len += (size_t)(strlen(current_name) + strlen(b.name) + strlen(b.msg));
	EC_NULL_E(log = malloc(log_len * sizeof(char)),ERR_MEM);
	snprintf(log,log_len,"%s:%s:%s",current_name,b.name,b.msg);
	//printf("snprintf log = %s strlen %d loglen %d\n",log, strlen(log), log_len);
	printf("TOONE AL WRITER SPARA !%s!\n",log);
	insert_queue(&writer_queue,log);

	//free di temp!!!!!!!!!
	//SVUOTARE IL BUFFER
	//alla fine mandare msg al writer...
	//una volta sbloccato dal writer
	//freeee qui o fuori
	return 1;
}


void userlist(){
	char *list = NULL;
	user_pointer_t *iter = NULL;
	user *u = NULL;
	size_t sz;

	/*Si assume che la lista non sia vuota, contiene almeno l'utente che ha chiesto la lista utenti*/
	iter = online_list->head;
	u = iter->user;
	//u = (user*)(iter->payload);
	/*Si copia il nome del primo user*/
	EC_NULL_E(list = strdup(u->name),ERR_MEM);
	sz = (size_t)(strlen(list)+1);
	/*Se non sono presenti altri si invia*/
	if(iter->next == NULL){
		newMessage(MSG_LIST,list, &response,sz);
		sendMessage(fd, &response);
		return;
	}

	iter = iter->next;
	while( iter != NULL){
		//u = (user*)iter->payload;
		u = iter->user;
		sz += (size_t)strlen(u->name)+3;//1 per nul byte 1 per spazio
		list = realloc(list,sz); //ec
		list = strcat(list, " ");
		list = strcat(list,u->name);
		iter = iter->next;
	}
	/*Lista pronta, si invia*/
	newMessage(MSG_LIST,list, &response,sz); //tanto per eh
	sendMessage(fd, &response);
	return;
}

void broadcast(message_t *msg){
	user_pointer_t *iter = NULL;
	user *u = NULL;
	char *text;
	char *log;
	size_t len = 3;//2 per le parentesi,spazio e  +1 \0
	size_t log_len;
	//printf("BROADCAST riceve !%s! strlen buf %d msg dim = %d\n",msg->buffer,strlen(msg->buffer),msg->length);

	/*Si alloca il buffer per il messaggio in broadcast, e si concatenano il nome utente con il messaggio*/
	len += (size_t)(msg->length + strlen(current_name));//lo spazio per \0 è compreso in msg len
	EC_NULL_E(text = malloc(len * sizeof(char)),ERR_MEM);
	snprintf(text,len,"[%s] %s",current_name,msg->buffer);
	newMessage(MSG_BCAST,text,&response,len);
	pthread_mutex_lock(&online_mtx);
	/*Si assume che la lista non sia vuota, contiene almeno l'utente mittente del broadcast*/
	iter = online_list->head;
	//occhio alle lock qui!!!!!!!!!!!!!!!!!!!11
	while( iter != NULL){
		u = iter->user;
		printf("BROADCAST SPEDISCE !%s! a !%s!\n",response.buffer, u->name);
		sendMessage(u->sfd,&response);
		/*Si prepara la riga per il log*/
		log_len = 3; /*i due : , \n e */ // \0 è in lenght
		log_len += (size_t)(strlen(current_name) + strlen(u->name) + msg->length);
		EC_NULL_E(log = malloc(log_len * sizeof(char)),ERR_MEM);
		snprintf(log,log_len,"%s:%s:%s",current_name,u->name,msg->buffer);
		//printf("snprintf log = %s strlen %d loglen %d\n",log, strlen(log), log_len);
		insert_queue(&writer_queue,log);
		/*I messaggi di log saranno distrutti dal writer*/
		iter = iter->next;
		//printf("iter next\n");

	}

	pthread_mutex_unlock(&online_mtx);

	free(response.buffer);
	response.buffer = NULL;
	response.length = 0;
	free(incoming.buffer);
	incoming.length = 0;
	incoming.buffer = NULL;
	return;
}



/**
 * Fuzione di chiusura del thread, si occupa di
 * -chiudere il socket
 * -rimuovere l'utente dalla lista utenti online
 * -chiamare la pthread_exit
 * Viene chiamata sia che l'utente desideri chiudere il client, sia in caso di chiusura del server, che in caso di errori di comunicazione
 */
void worker_stop(){

	if(pending_signal == 0){
		pthread_mutex_lock(&users_mtx);
		current_user->sfd = 0;
		remove_user_l(online_list, current_name);
		pthread_mutex_unlock(&users_mtx);
	}
	close(fd);
	pthread_exit(NULL);

}

/*Inserimento messaggio nella coda writer*/
/*Liberare la memoria allocata è compito del writer*/
void insert_queue(msg_queue *q,char *text){

	queue_node *np;
	/*Nuovo nodo della coda*/
	queue_node *new = malloc(sizeof(queue_node));
	new->text = text;
	new->next = NULL;

	pthread_mutex_lock(&queue_mtx);

	/*Inserimento in testa */
	if(q->head == NULL){
		q->head = new;
		//q->tail = n;
	}

	/*Inserimento in coda*/
	else{
		np = q->head;
		while(np != NULL){
			np = np->next;
		}
		np = new;
//		q->tail->next = n;
//		q->tail = n;
	}
	pthread_cond_signal(&empty_cond);
	pthread_mutex_unlock(&queue_mtx);
	return;
}

static void sigpipe_handler(int signum) {
	sgpipe = 1;
}
static void sigusr1_handler(int signum){
	worker_stop();
}
